package ru.qiwi.security;



public class MD5
{

	static final byte[] MD5_DER_HEADER = new byte[]{0x30, 0x20, 0x30, 0x0C, 0x06, 0x08, 0x2A, ( byte ) 0x86, 0x48, ( byte ) 0x86,
		( byte ) 0xF7, 0x0D, 0x02, 0x05, 0x05, 0x00, 0x4, 0x10};

	static final byte MD5_BEFORE_PADDING = 0x01;
	
	static final byte MD5_PADDING = ( byte ) 0xFF;
	
	static final byte MD5_AFTER_PADDING = 0x00;

	
	int buf[];	
	long bits;	
	
	byte in[];	
	int inint[];
	
	public static byte[] md5( String s ) { return md5( s.getBytes() ); }

	public static String hashAsHex (String data) { return asHex(md5(data)) ; }
	
	public static String asHex (byte hash[]) {
		StringBuffer buf = new StringBuffer(hash.length * 2);
		int i;
		for (i = 0; i < hash.length; i++) {
			if (((int) hash[i] & 0xff) < 0x10)
				buf.append('0');
			buf.append(Integer.toString((int) hash[i] & 0xff, 16));
		}
		return buf.toString();
	}

	
	public static byte[] md5( byte[] b ) {
		byte out[] = new byte[16];
		MD5 md5 = new MD5();
		md5.update( b );
		md5.md5final( out );
		return out;
	}

	
	public MD5()
	{
		buf = new int[4];
		// fill the hash accumulator with a seed value
		buf[0] = 0x67452301;
		buf[1] = 0xefcdab89;
		buf[2] = 0x98badcfe;
		buf[3] = 0x10325476;

		// initially, we've hashed zero bits
		bits = 0;

		in = new byte[64];
		inint = new int[16];
	}

	
	void update( byte[] newbuf )
	{
		int bufstart = 0;
		//final int buflen = newbuf.length;
		int t;
		int len = newbuf.length;

		// shash old bits value for the "Bytes already in" computation
		// just below.
		t = ( int ) bits;	// (int) cast should just drop high bits, I hope

		/* update bitcount */
		/* the C code used two 32-bit ints separately, and carefully
		 * ensured that the carry carried.
		 * Java has a 64-bit long, which is just what the code really wants.
		 */
		bits += ( long ) ( len << 3 );

		t = ( t >>> 3 ) & 0x3f;	/* Bytes already in this->in */

		/* Handle any leading odd-sized chunks */
		/* (that is, any left-over chunk left by last update() */

		if( t != 0 )
		{
			int p = t;
			t = 64 - t;
			if( len < t )
			{
				System.arraycopy( newbuf, bufstart, in, p, len );
				return;
			}
			System.arraycopy( newbuf, bufstart, in, p, t );
			transform();
			bufstart += t;
			len -= t;
		}

		/* Process data in 64-byte chunks */
		while( len >= 64 )
		{
			System.arraycopy( newbuf, bufstart, in, 0, 64 );
			transform();
			bufstart += 64;
			len -= 64;
		}

		/* Handle any remaining bytes of data. */
		/* that is, stash them for the next update(). */
		System.arraycopy( newbuf, bufstart, in, 0, len );
	}

	/*
	 * Final wrapup - pad to 64-byte boundary with the bit pattern
	 * 1 0* (64-bit count of bits processed, MSB-first)
	 */
	void md5final( byte[] digest )
	{
		/* "final" is a poor method name in Java. :v) */
		int count;
		int p;		// in original code, this is a pointer; in this java code
		// it's an index into the array this->in.

		/* Compute number of bytes mod 64 */
		count = ( int ) ( ( bits >>> 3 ) & 0x3F );

		/* Set the first char of padding to 0x80.  This is safe since there is
						 always at least one byte free */
		p = count;
		in[p++] = ( byte ) 0x80;

		/* Bytes of padding needed to make 64 bytes */
		count = 64 - 1 - count;

		/* Pad out to 56 mod 64 */
		if( count < 8 )
		{
			/* Two lots of padding:  Pad the first block to 64 bytes */
			zeroByteArray( in, p, count );
			transform();

			/* Now fill the next block with 56 bytes */
			zeroByteArray( in, 0, 56 );
		}
		else
		{
			/* Pad block to 56 bytes */
			zeroByteArray( in, p, count - 8 );
		}

		/* Append length in bits and transform */
		// Could use a PUT_64BIT... func here. This is a fairly
		// direct translation from the C code, where bits was an array
		// of two 32-bit ints.
		int lowbits = ( int ) bits;
		int highbits = ( int ) ( bits >>> 32 );
		PUT_32BIT_LSB_FIRST( in, 56, lowbits );
		PUT_32BIT_LSB_FIRST( in, 60, highbits );

		transform();
		PUT_32BIT_LSB_FIRST( digest, 0, buf[0] );
		PUT_32BIT_LSB_FIRST( digest, 4, buf[1] );
		PUT_32BIT_LSB_FIRST( digest, 8, buf[2] );
		PUT_32BIT_LSB_FIRST( digest, 12, buf[3] );
	}

	/////////////////////////////////////////////////////////////////////
	// Below here ye will only finde private functions                 //
	/////////////////////////////////////////////////////////////////////

	private void zeroByteArray( byte[] a, int start, int length )
	{
		//		setByteArray(a, (byte) 0, start, length);
		int i;
		int end = start + length;
		for( i = start; i < end; i++ )
		{
			a[i] = 0;
		}
	}

	// In the C VERSION, a call to MD5STEP is a macro-in-a-macro.
	// In this Java VERSION, we pass an Fcore object to represent the
	// inner macro, and the MD5STEP() method performs the work of
	// the outer macro. It would be good if this could all getBoolean
	// inlined, but it would take a pretty aggressive compiler to
	// inline away the dynamic method lookup made by MD5STEP to
	// getBoolean to the Fcore.f function.
                 /*
	private abstract class Fcore
	{
		abstract int f( int x, int y, int z );
	}

	private Fcore F1 = new Fcore()
	{
		int f( int x, int y, int z )
		{
			return ( z ^ ( x & ( y ^ z ) ) );
		}
	};
	private Fcore F2 = new Fcore()
	{
		int f( int x, int y, int z )
		{
			return ( y ^ ( z & ( x ^ y ) ) );
		}
	};
	private Fcore F3 = new Fcore()
	{
		int f( int x, int y, int z )
		{
			return ( x ^ y ^ z );
		}
	};
	private Fcore F4 = new Fcore()
	{
		int f( int x, int y, int z )
		{
			return ( y ^ ( x | ~z ) );
		}
	};
                  */
/*
    private class FX {
        int f(int x, int y, int z, int n) {
            if (n==1) return z ^ ( x & ( y ^ z ) );
            if (n==2) return y ^ ( z & ( x ^ y ) );
            if (n==3) return x ^ y ^ z;
            return y ^ ( x | ~z );
        }
    }
    private FX Fx = new FX();
*/
    int f(int x, int y, int z) {
        if (n==1) return z ^ ( x & ( y ^ z ) );
        if (n==2) return y ^ ( z & ( x ^ y ) );
        if (n==3) return x ^ y ^ z;
        return y ^ ( x | ~z );
    }

    int n = 0;

	private int MD5STEP( /*FX f, int n,*/ int w, int x, int y, int z, int data, int s )
	{
		w += f( x, y, z ) + data;
		w = w << s | w >>> ( 32 - s );
		w += x;
		return w;
	}

	private void transform()
	{
		/* load in[] byte array into an internal int array */
		int i;
		int[] inint = new int[16];

		for( i = 0; i < 16; i++ )
			inint[i] = GET_32BIT_LSB_FIRST( in, 4 * i );

        
		int a, b, c, d;
		a = buf[0];
		b = buf[1];
		c = buf[2];
		d = buf[3];

        n = 1;
		a = MD5STEP( /*Fx,1, */ a, b, c, d, inint[0] + 0xd76aa478, 7 );
		d = MD5STEP( /*Fx,1, */ d, a, b, c, inint[1] + 0xe8c7b756, 12 );
		c = MD5STEP( /*Fx,1, */ c, d, a, b, inint[2] + 0x242070db, 17 );
		b = MD5STEP( /*Fx,1, */ b, c, d, a, inint[3] + 0xc1bdceee, 22 );
		a = MD5STEP( /*Fx,1, */ a, b, c, d, inint[4] + 0xf57c0faf, 7 );
		d = MD5STEP( /*Fx,1, */ d, a, b, c, inint[5] + 0x4787c62a, 12 );
		c = MD5STEP( /*Fx,1, */ c, d, a, b, inint[6] + 0xa8304613, 17 );
		b = MD5STEP( /*Fx,1, */ b, c, d, a, inint[7] + 0xfd469501, 22 );
		a = MD5STEP( /*Fx,1, */ a, b, c, d, inint[8] + 0x698098d8, 7 );
		d = MD5STEP( /*Fx,1, */ d, a, b, c, inint[9] + 0x8b44f7af, 12 );
		c = MD5STEP( /*Fx,1, */ c, d, a, b, inint[10] + 0xffff5bb1, 17 );
		b = MD5STEP( /*Fx,1, */ b, c, d, a, inint[11] + 0x895cd7be, 22 );
		a = MD5STEP( /*Fx,1, */ a, b, c, d, inint[12] + 0x6b901122, 7 );
		d = MD5STEP( /*Fx,1, */ d, a, b, c, inint[13] + 0xfd987193, 12 );
		c = MD5STEP( /*Fx,1, */ c, d, a, b, inint[14] + 0xa679438e, 17 );
		b = MD5STEP( /*Fx,1, */ b, c, d, a, inint[15] + 0x49b40821, 22 );

        n=2;
		a = MD5STEP( /*Fx,2, */ a, b, c, d, inint[1] + 0xf61e2562, 5 );
		d = MD5STEP( /*Fx,2, */ d, a, b, c, inint[6] + 0xc040b340, 9 );
		c = MD5STEP( /*Fx,2, */ c, d, a, b, inint[11] + 0x265e5a51, 14 );
		b = MD5STEP( /*Fx,2, */ b, c, d, a, inint[0] + 0xe9b6c7aa, 20 );
		a = MD5STEP( /*Fx,2, */ a, b, c, d, inint[5] + 0xd62f105d, 5 );
		d = MD5STEP( /*Fx,2, */ d, a, b, c, inint[10] + 0x02441453, 9 );
		c = MD5STEP( /*Fx,2, */ c, d, a, b, inint[15] + 0xd8a1e681, 14 );
		b = MD5STEP( /*Fx,2, */ b, c, d, a, inint[4] + 0xe7d3fbc8, 20 );
		a = MD5STEP( /*Fx,2, */ a, b, c, d, inint[9] + 0x21e1cde6, 5 );
		d = MD5STEP( /*Fx,2, */ d, a, b, c, inint[14] + 0xc33707d6, 9 );
		c = MD5STEP( /*Fx,2, */ c, d, a, b, inint[3] + 0xf4d50d87, 14 );
		b = MD5STEP( /*Fx,2, */ b, c, d, a, inint[8] + 0x455a14ed, 20 );
		a = MD5STEP( /*Fx,2, */ a, b, c, d, inint[13] + 0xa9e3e905, 5 );
		d = MD5STEP( /*Fx,2, */ d, a, b, c, inint[2] + 0xfcefa3f8, 9 );
		c = MD5STEP( /*Fx,2, */ c, d, a, b, inint[7] + 0x676f02d9, 14 );
		b = MD5STEP( /*Fx,2, */ b, c, d, a, inint[12] + 0x8d2a4c8a, 20 );

        n=3;
		a = MD5STEP( /*Fx,3, */ a, b, c, d, inint[5] + 0xfffa3942, 4 );
		d = MD5STEP( /*Fx,3, */ d, a, b, c, inint[8] + 0x8771f681, 11 );
		c = MD5STEP( /*Fx,3, */ c, d, a, b, inint[11] + 0x6d9d6122, 16 );
		b = MD5STEP( /*Fx,3, */ b, c, d, a, inint[14] + 0xfde5380c, 23 );
		a = MD5STEP( /*Fx,3, */ a, b, c, d, inint[1] + 0xa4beea44, 4 );
		d = MD5STEP( /*Fx,3, */ d, a, b, c, inint[4] + 0x4bdecfa9, 11 );
		c = MD5STEP( /*Fx,3, */ c, d, a, b, inint[7] + 0xf6bb4b60, 16 );
		b = MD5STEP( /*Fx,3, */ b, c, d, a, inint[10] + 0xbebfbc70, 23 );
		a = MD5STEP( /*Fx,3, */ a, b, c, d, inint[13] + 0x289b7ec6, 4 );
		d = MD5STEP( /*Fx,3, */ d, a, b, c, inint[0] + 0xeaa127fa, 11 );
		c = MD5STEP( /*Fx,3, */ c, d, a, b, inint[3] + 0xd4ef3085, 16 );
		b = MD5STEP( /*Fx,3, */ b, c, d, a, inint[6] + 0x04881d05, 23 );
		a = MD5STEP( /*Fx,3, */ a, b, c, d, inint[9] + 0xd9d4d039, 4 );
		d = MD5STEP( /*Fx,3, */ d, a, b, c, inint[12] + 0xe6db99e5, 11 );
		c = MD5STEP( /*Fx,3, */ c, d, a, b, inint[15] + 0x1fa27cf8, 16 );
		b = MD5STEP( /*Fx,3, */ b, c, d, a, inint[2] + 0xc4ac5665, 23 );

        n=4;
		a = MD5STEP( /*Fx,4, */ a, b, c, d, inint[0] + 0xf4292244, 6 );
		d = MD5STEP( /*Fx,4, */ d, a, b, c, inint[7] + 0x432aff97, 10 );
		c = MD5STEP( /*Fx,4, */ c, d, a, b, inint[14] + 0xab9423a7, 15 );
		b = MD5STEP( /*Fx,4, */ b, c, d, a, inint[5] + 0xfc93a039, 21 );
		a = MD5STEP( /*Fx,4, */ a, b, c, d, inint[12] + 0x655b59c3, 6 );
		d = MD5STEP( /*Fx,4, */ d, a, b, c, inint[3] + 0x8f0ccc92, 10 );
		c = MD5STEP( /*Fx,4, */ c, d, a, b, inint[10] + 0xffeff47d, 15 );
		b = MD5STEP( /*Fx,4, */ b, c, d, a, inint[1] + 0x85845dd1, 21 );
		a = MD5STEP( /*Fx,4, */ a, b, c, d, inint[8] + 0x6fa87e4f, 6 );
		d = MD5STEP( /*Fx,4, */ d, a, b, c, inint[15] + 0xfe2ce6e0, 10 );
		c = MD5STEP( /*Fx,4, */ c, d, a, b, inint[6] + 0xa3014314, 15 );
		b = MD5STEP( /*Fx,4, */ b, c, d, a, inint[13] + 0x4e0811a1, 21 );
		a = MD5STEP( /*Fx,4, */ a, b, c, d, inint[4] + 0xf7537e82, 6 );
		d = MD5STEP( /*Fx,4, */ d, a, b, c, inint[11] + 0xbd3af235, 10 );
		c = MD5STEP( /*Fx,4, */ c, d, a, b, inint[2] + 0x2ad7d2bb, 15 );
		b = MD5STEP( /*Fx,4, */ b, c, d, a, inint[9] + 0xeb86d391, 21 );

		buf[0] += a;
		buf[1] += b;
		buf[2] += c;
		buf[3] += d;
	}

	private int GET_32BIT_LSB_FIRST( byte[] b, int off )
	{
		return ( ( b[off] & 0xff ) ) | (  (b[off + 1] & 0xff ) << 8 ) | ( ( b[off + 2] & 0xff ) << 16 ) |
		( ( b[off + 3] & 0xff ) << 24 );
	}

	private void PUT_32BIT_LSB_FIRST( byte[] b, int off, int value )
	{
		b[off] = ( byte ) ( value/* & 0xff */);
		b[off + 1] = ( byte ) ( ( value >> 8 )/* & 0xff */);
		b[off + 2] = ( byte ) ( ( value >> 16 )/* & 0xff */);
		b[off + 3] = ( byte ) ( ( value >> 24 )/* & 0xff */);
	}

}
